/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						       creature.cpp

	$Header: /game/creature.cpp $

	$NoKeywords: $

 ************************************************************************/

#include "precompiled.h"
#include "creature.h"

#include <assert.h>
#include "adventure_map.h"
#include "creature_ability.h"
#include "creature_array.h"
#include "creature_traits.h"
#include "creature_type.h"
#include "get_artifact_damage_modifier.h"
#include "player.h"
#include "random.h"
#include "random_monster_experience.h"
#include "sized_int_types.h"
#include "spell.h"
#include "spell_properties.h"
#include "streambuf_operators.h"
#include "terrain.h"
#include "town_type.h"

// -----------------------------------------------------------------------
// unnamed namespace
// -----------------------------------------------------------------------

namespace
{

	int const k_current_map_format_version = 1;

} // unnamed namespace

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
t_creature::t_creature()
{
	m_creature = k_creature_none;
	m_number = 0;
	m_growth = 0;
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
t_creature::t_creature( t_creature_type creature, int amount )
{
	m_creature = creature;
	m_number = amount;
	m_growth = 0;
}
	
// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
void t_creature::add( t_creature const& arg )
{
	assert( m_creature == arg.m_creature);

	int i;

	if (get_raw_adventure_movement_left() > arg.get_raw_adventure_movement_left())
		set_raw_adventure_movement( arg.get_raw_adventure_movement_left() );
	m_number += arg.m_number;
	for (i = 0; i < arg.m_backpack.size(); i++)
		m_backpack.push_back( arg.m_backpack[i] );
	m_active_spells &= arg.m_active_spells;
	// use only bonuses we can find in the other stack.
	i = m_bonus_array.size();
	while (i--)
	{
		t_temporary_bonus& bonus = m_bonus_array[i];

		if (bonus.amount < 0)
			continue;

		int j;

		j = arg.m_bonus_array.size();
		while (j--)
		{
			t_temporary_bonus const& other_bonus = arg.m_bonus_array[j];

			if (other_bonus.amount == bonus.amount 
				&& other_bonus.object_type == bonus.object_type
				&& other_bonus.stat == bonus.stat )
				break;
		}
		if (j < 0)
		{
			m_bonus[bonus.stat] -= bonus.amount;
			m_bonus_array.erase( m_bonus_array.begin() + i );
		}
	}

	i = m_timed_bonus_array.size();
	while (i--)
	{
		t_timed_bonus& bonus = m_timed_bonus_array[i];

		if (bonus.amount < 0)
			continue;

		int j;

		j = arg.m_timed_bonus_array.size();
		while (j--)
		{
			t_timed_bonus const& other_bonus = arg.m_timed_bonus_array[j];

			if (other_bonus.amount == bonus.amount 
				&& other_bonus.object_type == bonus.object_type
				&& other_bonus.stat == bonus.stat )
			{
				if (other_bonus.days_left < bonus.days_left)
					bonus.days_left = other_bonus.days_left;
				break;
			}
		}
		if (j < 0)
		{
			m_bonus[bonus.stat] -= bonus.amount;
			m_timed_bonus_array.erase( m_timed_bonus_array.begin() + i );
		}
	}

}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
bool t_creature::can_add( t_creature_type creature ) const
{
	return creature == m_creature;
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
bool t_creature::can_add( t_creature_stack const& arg ) const
{
	if (arg.get_creature() == 0)
		return false;
	return m_creature == arg.get_creature()->get_creature_type();
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
int	t_creature::get_damage_high() const
{
	return get_traits().damage_high;

} 

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
float t_creature::get_offense( bool ranged ) const
{
	float result = get_traits().attack_skill;

	if (!ranged && has_ability( k_ability_ranged ) && !has_ability( k_ability_normal_melee ))
		return result * 0.5;
	return result;
}

float t_creature::get_offense( bool ranged, t_creature_array const& army ) const
{
	t_hero const*	hero;
	int				bonus;
	int				highest_bonus = 0;
	int				i;

	for (i = 0; i < t_creature_array::k_size; ++i)
	{
		hero = army[i].get_hero();
		if (hero == 0)
			continue;
		bonus = get_artifact_creature_attack_bonus( *hero )
			  + hero->get_tactics_offense_bonus( army.is_boat() );
		if (highest_bonus < bonus)
			highest_bonus = bonus;
	}
	return get_offense( ranged ) * (highest_bonus + 100) * 0.01;
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
float t_creature::get_defense_basic( bool ranged ) const
{
	return get_traits().defense_skill;
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
float t_creature::get_defense_bonus( bool ranged ) const
{
	float result = 1.0f;

	if (has_ability( k_ability_insubstantial ))
		result *= 2.0f;
	if (ranged && has_ability( k_ability_skeletal ))
		result *= 2.0f;
	return result;
}

float t_creature::get_defense( bool ranged, t_creature_array const& army ) const
{
	t_hero const*	hero;
	int				bonus;
	int				highest_bonus = 0;
	int				i;

	for (i = 0; i < t_creature_array::k_size; ++i)
	{
		hero = army[i].get_hero();
		if (hero == 0)
			continue;
		bonus = get_artifact_creature_defense_bonus( *hero, ranged )
			  + hero->get_tactics_defense_bonus( army.is_boat() );
		if (highest_bonus < bonus)
			highest_bonus = bonus;
	}
	return get_defense_basic( ranged ) * get_defense_bonus( ranged ) 
		 * (highest_bonus + 100) * 0.01;
}


// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
float t_creature::get_defense_reduction( bool ranged ) const
{
	return 1.0;
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
int	t_creature::get_damage_low() const
{
	return get_traits().damage_low;
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
int t_creature::get_experience_value( int number ) const
{
	if (number < 0)
		number = m_number;
	return get_traits().experience * number;
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
t_creature const*	t_creature::get_const_creature() const
{
	return this;
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
int	t_creature::get_hit_points() const
{
	return get_traits().hit_points;
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
int t_creature::get_morale( t_creature_array const& army ) const
{
	if (has_ability( k_ability_undead ) || has_ability( k_ability_mechanical ))
		return 0;
	return t_stack_with_backpack::get_morale( army );
}

int t_creature::get_morale_bonus( t_hero const& hero ) const
{
	return t_stack_with_backpack::get_morale_bonus( hero ) + hero.get_leadership_morale_bonus();
}

int t_creature::get_luck_bonus( t_hero const& hero ) const
{
	return t_stack_with_backpack::get_luck_bonus( hero ) + hero.get_leadership_luck_bonus();
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
int t_creature::get_move_cost( t_terrain_type terrain ) const
{
	int result = t_stack_with_backpack::get_move_cost( terrain );

	if (is_native_terrain( terrain ) && result > 100)
	{
		result -= 50;
		if (result < 100)
			result = 100;
	}
	return result;
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
bool t_creature::has_ability( t_creature_ability ability ) const
{
	return ::has_ability( m_creature, ability );
}

bool t_creature::is_active() const
{
	return true;
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
int t_creature::get_combat_movement() const
{
	return get_traits().movement * 100;
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
std::string t_creature::get_army_name() const
{
	return get_army_size_name( m_number, k_article_a ) 
		 + " " + get_creature_name( m_creature, 2, false );
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
std::string t_creature::get_name( bool include_article, int number ) const
{
	if (number < 0)
		number = m_number;
	return get_creature_name( m_creature, number, include_article );
}

// ------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
int	t_creature::get_number() const
{
	return m_number;
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
t_bitmap_layer_ptr t_creature::get_portrait( int height, bool force_awake_portrait ) const
{
	return get_creature_portrait( m_creature, height );
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
int	t_creature::get_shots() const
{
	return get_traits().shots;
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
int t_creature::get_speed() const
{
	return get_traits().speed;
}

int t_creature::get_speed( t_creature_array const& army ) const
{
	t_hero const* hero;
	int		bonus;
	int		highest_bonus = 0;
	int		i;

	for (i = 0; i < t_creature_array::k_size; ++i)
	{
		hero = army[i].get_hero();
		if (hero == 0)
			continue;
		bonus = get_artifact_speed_modifier( *hero, k_artifact_target_friendly_creatures )
			  + hero->get_tactics_speed_bonus( army.is_boat() );
		if (highest_bonus < bonus)
			highest_bonus = bonus;
	}
	return get_speed() + highest_bonus;
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
int	t_creature::get_maximum_spell_points() const
{
	return get_traits().spell_points;
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
int	t_creature::get_spell_points() const
{
	return get_traits().spell_points;
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
void t_creature::remove( int amount )
{
	m_number -= amount;
	assert( m_number > 0 );
}

t_creature_traits const& t_creature::get_traits() const
{
	return ::get_traits( m_creature );
}

// ------------------------------------------------------------
// object with either a hero, a set of creatures, or nothing.
// ------------------------------------------------------------
bool t_creature::knows_spell( t_spell spell ) const
{
	return ::knows_spell( m_creature, spell );
}

// ------------------------------------------------------------
// Power level for spell
// ------------------------------------------------------------
int t_creature::get_spell_power( t_spell spell, t_abstract_grail_data_source const & grail_data, int number ) const
{
	if (number < 0)
		number = m_number;

	int bonus = 100 + grail_data.get_grail_spell_bonus( get_spell_alignment( spell ) );

	// rebirth always has the same strength, even if some creatures are dead.
	if (spell == k_spell_rebirth)
		return ::get_basic_spell_power( spell, 0, bonus * m_number );

	if (spell_is( spell, k_spell_bit_summoning | k_spell_bit_damage, false )
		|| spell == k_spell_resurrection)
		return ::get_basic_spell_power( spell, 0, get_traits().spell_power * number * bonus / 100 );
	
	return ::get_basic_spell_power( spell, 0, bonus );
}

// -----------------------------------------------------------------------
// stack of creatures
// get creature's alignment
// -----------------------------------------------------------------------
t_town_type t_creature::get_alignment() const
{
	return ::get_traits( m_creature ).alignment;
}

bool t_creature::is_native_terrain( t_terrain_type terrain ) const
{
	if (terrain >= k_basic_terrain_count)
		return false;

	if (has_terrain_mastery( terrain ))
		return true;

	static t_terrain_type const k_native_terrains[k_town_type_count] =
	{
		k_terrain_grass, // k_town_life
		k_terrain_snow, // k_town_order,
		k_terrain_lava, //k_town_death,
		k_terrain_swamp, // k_town_chaos,
		k_terrain_grass, // k_town_nature,
		k_terrain_rough, // k_town_might,
	};

	return terrain == k_native_terrains[::get_traits( m_creature ).alignment];
}

// -----------------------------------------------------------------------
// stack of creatures
// check if creature is undead
// -----------------------------------------------------------------------
bool t_creature::is_undead() const
{
	return ::has_ability( m_creature, k_ability_undead );
}

// -----------------------------------------------------------------------
// stack of creatures
// return correct combat model
// -----------------------------------------------------------------------
t_combat_actor_model_ptr t_creature::get_combat_model( double scale ) const
{
	return get_combat_actor_model( m_creature, scale );
}

// -----------------------------------------------------------------------
// stack of creatures
// return the type of missile fired in combat
// -----------------------------------------------------------------------
t_missile_type t_creature::get_missile_type() const
{
	return ::get_missile_type( m_creature );
}

// -----------------------------------------------------------------------
// stack of creatures
// write to file
// -----------------------------------------------------------------------
bool t_creature::write( std::streambuf& stream ) const
{
	write_header( stream );
	put<t_creature_type>( stream, m_creature );
	put<int>( stream, m_number );
	put<t_uint16>( stream, m_growth );
	return true;
}

// -----------------------------------------------------------------------
// stack of creatures
// read from savegame
// -----------------------------------------------------------------------
bool t_creature::read( std::streambuf& stream )
{
	int version;

	read_header( stream, version );
	m_creature = get<t_creature_type>( stream );
	m_number = get<int>( stream );
	if (version < 2)
		m_growth = 0;
	else
		m_growth = get<t_uint16>( stream );
	return true;
}

// -----------------------------------------------------------------------
// stack of creatures
// read from map
// -----------------------------------------------------------------------
bool t_creature::read_from_map( std::streambuf& stream )
{
	int version = get< t_uint16 >( stream );
	if ( version < 0 || version > k_current_map_format_version )
		return false;

	t_creature_type new_creature_type = t_creature_type( get< t_int16 >( stream ) );
	if (	new_creature_type != k_creature_none
		&&	( new_creature_type < 0 || new_creature_type >= k_creature_type_count ) )
		return false;

	int new_quantity = get< t_int16 >( stream );

	m_growth = 0;
	if ( new_quantity == 0 )
	{
		t_creature_traits const& traits = ::get_traits( new_creature_type );

		new_quantity = get_random_monster_experience( traits.level ) << 8;
		new_quantity = new_quantity / traits.experience;
		if (new_quantity < 256)
			new_quantity = 256;
		m_growth = new_quantity & 0xFF;
		new_quantity = new_quantity >> 8;
	}

	if ( version >= 1 && !t_stack_with_backpack::read_from_map( stream ) )
		return false;

	m_creature = new_creature_type;
	m_number = new_quantity;

	return true;
}

// preplacement.  Convert randomized amounts for unowned stacks to a different value.
bool t_creature::preplacement( t_adventure_map& map, int pass, t_player* owner )
{
	if( t_creature_stack::preplacement( map, pass, owner ))
		return true;
	if ( owner != 0 )
		return false;

	// adjust size of stack by difficulty level
	int new_quantity = (m_number << 8) + m_growth;

	new_quantity *= get_monster_difficulty( map.get_player_difficulty() );
	if (new_quantity < 256)
		new_quantity = 256;
	m_number = new_quantity >> 8;
	m_growth = new_quantity & 0xFF;
	return false;
}

// -----------------------------------------------------------------------
// stack of creatures
// get daily income
// -----------------------------------------------------------------------
void t_creature::get_income( int day, t_material_array& income ) const
{
	if (m_creature == k_peasant)
		income[k_gold] += m_number;
}

// -----------------------------------------------------------------------
// stack of creatures
// neutral creature growth
// -----------------------------------------------------------------------
void t_creature::process_new_day( t_player* owner, bool grow, t_creature_array* army, int day )
{
	t_stack_with_backpack::process_new_day( owner, grow, army, day );

    if (m_creature == k_peasant && owner != 0)
		owner->gain( k_gold, get_number() );
        
	if (owner != 0 || !grow)
		return;  // only neutral stacks grow

	int growth = (m_number << 8) / 10;
	int limit = (get_traits().weekly_growth << 8);

	if (growth > limit)
		growth = limit;
	m_growth += growth / 7;
	m_number += m_growth >> 8;
	m_growth &= 0xFF;
}

// -----------------------------------------------------------------------
// stack of creatures
// chance of one creature being hit by stone gaze
// -----------------------------------------------------------------------
int t_creature::get_stoning_chance() const
{
	static int const k_stoning_chance[] = { 250, 100, 50, 20 };

	int level = get_traits().level;

	assert( level >= 1 && level <= 4 );
	return k_stoning_chance[ level - 1 ];
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
int t_creature::compute_scouting_range() const
{
	return 9;
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
t_creature_stack::t_clone_ptr t_creature::clone() const
{
	t_creature* ptr = new t_creature( *this );
	return ptr;
}

// -----------------------------------------------------------------------
// stack of creatures
// -----------------------------------------------------------------------
int t_creature::get_magic_resistance( t_player const* owner, t_spell spell ) const
{
	if ( ::has_ability( m_creature, k_ability_magic_immunity ) )
		return 100;

	switch ( m_creature )
	{
		case k_gold_golem:
			return 75;

		case k_dwarf:
		case k_earth_elemental:
			return 50;
	}

	return 0;
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
t_skill_mastery t_creature::get_anti_stealth_level() const
{
	return t_skill_mastery( k_mastery_none + ( get_traits().level - 1 ) );
}

t_skill_mastery t_creature::get_stealth_level() const
{
	if (get_traits().has_ability( k_ability_stealth ))
		return k_mastery_advanced;
	return k_mastery_none;
}

// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
std::string	t_creature::get_right_click_text() const
{
	return get_traits().help_text; 
}


